/* Copyright Thanh-Trung Pham, JAIST, 2012 */

package jp.ac.jaist.cli;

import java.io.File;
import java.io.IOException;
import java.util.List;
import jp.ac.jaist.InternalActivityType;
import jp.ac.jaist.UserInterface;
import jp.ac.jaist.computation.RMReliabilityCalculator;
import jp.ac.jaist.computation.RMSensitivityAnalyser;
import jp.ac.jaist.computation.RMSensitivityAnalysisData;
import jp.ac.jaist.message.RMInputModelException;
import jp.ac.jaist.input.RMInputModelManager;
import jp.ac.jaist.message.RMCLIException;
import jp.ac.jaist.message.RMMessageResource;
import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.xmlbeans.XmlError;

/**
 *
 * @author Thanh-Trung Pham
 */
public class RMReliabilityModeling {

    private static final String USAGE = "[-h] [-v] [-p] [-s] <input_model_file> <output_file>";
    private static final String HEADER = "SRM - A reliability modeling tool, Copyright 2012 Thanh-Trung Pham";
    private static final String FOOTER = "For more instructions, contact us at: thanhtrung.pham@outlook.com";
    private String m_oCommandLineArgs[];
    private boolean m_isGNUPLOTFolderCreated = false;
    private int m_iGNUPLOTDataIndex = -1;

    /**
     * The constructor.
     * @param commandLineArgs an array of command line arguments.
     */
    public RMReliabilityModeling(String[] commandLineArgs) {
        m_oCommandLineArgs = commandLineArgs;
    }

    /**
     * Start the reliability modeling tool.
     */
    private void doStart() {

        OptionGroup oOptionGroup = new OptionGroup();
        oOptionGroup.addOption(OptionBuilder.hasArg(false).withDescription("Print the usage information").create('h'));
        oOptionGroup.addOption(OptionBuilder.hasArg(false).withDescription("Only conduct a validation on the input model").create('v'));
        oOptionGroup.addOption(OptionBuilder.hasArg(false).withDescription("Only conduct a prediction on the input model").create('p'));
        oOptionGroup.addOption(OptionBuilder.hasArg(false).withDescription("Only conduct sensitivity analyses on the input model").create('s'));

        CommandLineParser oParser = new BasicParser();
        Options oOptions = new Options();
        oOptions.addOptionGroup(oOptionGroup);
        try {
            CommandLine oCommandLine = oParser.parse(oOptions, m_oCommandLineArgs);
            if (oCommandLine.hasOption('h')) {
                printUsageInformation(oOptions);
                return;
            }

            String[] args = oCommandLine.getArgs();
            if (args == null || args.length != 2 || StringUtils.isBlank(args[0]) || StringUtils.isBlank(args[1])) {
                printInformation("Bad command line arguments were provided!\n", false);
                printUsageInformation(oOptions);
                return;
            }

            String strInputModelFilename = args[0];
            String strOutputFilename = args[1];

            StringBuilder oOutputContent = new StringBuilder();

            boolean isValidInputModel = validateInputRelabilityModeling(strInputModelFilename, oOutputContent);
            if (!isValidInputModel) {
                IOUtils.write(oOutputContent.toString(), new java.io.FileOutputStream(strOutputFilename));
                return;
            }

            if (oCommandLine.hasOption('v')) {
                IOUtils.write(oOutputContent.toString(), new java.io.FileOutputStream(strOutputFilename));
                return;
            }

            RMInputModelManager oManager = RMInputModelManager.getInstance();
            UserInterface[] oUserInterfaces = oManager.getUserInterfaces();


            if (oCommandLine.hasOption('p')) {
                printInformation("Conducting a reliabilty prediction... ", true);
                for (UserInterface oUserInterface : oUserInterfaces) {
                    String strUserInterfaceName = oUserInterface.getName();
                    conductReliabilityPrediction(strUserInterfaceName, oOutputContent);
                }
                IOUtils.write(oOutputContent.toString(), new java.io.FileOutputStream(strOutputFilename));
                printInformation("Done.", false);
                printInformation("Check the output file for detailed results.", true);
                return;
            }

            if (oCommandLine.hasOption('s')) {
                printInformation("Conducting sensitivity analyses... ", true);
                for (UserInterface oUserInterface : oUserInterfaces) {
                    String strUserInterfaceName = oUserInterface.getName();
                    conductSensivitityAnalyses(strOutputFilename, strUserInterfaceName, oOutputContent);
                }

                IOUtils.write(oOutputContent.toString(), new java.io.FileOutputStream(strOutputFilename));
                printInformation("Done.", false);
                printInformation("Check the output file for detailed results.", true);
                return;
            }

            printInformation("Conducting a reliability prediction and sensitivity analyses... ", true);
            for (UserInterface oUserInterface : oUserInterfaces) {
                String strUserInterfaceName = oUserInterface.getName();
                conductReliabilityPrediction(strUserInterfaceName, oOutputContent);
                conductSensivitityAnalyses(strOutputFilename, strUserInterfaceName, oOutputContent);
            }
            IOUtils.write(oOutputContent.toString(), new java.io.FileOutputStream(strOutputFilename));
            printInformation("Done.", false);
            printInformation("Check the output file for detailed results.", true);
        } catch (IOException oIOException) {
            RMCLIException oRMCLIException = new RMCLIException(RMMessageResource.CLI_UNEXPECTED_EXCEPTION, oIOException, false);
            printErrorInformation(oRMCLIException);
        } catch (ParseException oParseException) {
            printErrorInformation(oParseException);
            printUsageInformation(oOptions);
        }
    }

    /**
     * Print the error information.
     * @param exception the exception caused the error.
     */
    private void printErrorInformation(Exception exception) {
        System.out.println(exception.getMessage());
    }

    /**
     * Print information.
     * @param information information to print.
     * @param isOnNewLine if true, the information will be on a new line, otherwise false.
     */
    private void printInformation(String information, boolean isOnNewLine) {
        if (isOnNewLine) {
            System.out.println();
        }

        System.out.print(information);
    }

    /**
     * Check if the gnu plot folder has been created.
     * @return true if the gnu plot folder has been created, otherwise false.
     */
    private boolean isGNUPLOTFolderCreated() {
        return m_isGNUPLOTFolderCreated;
    }

    /**
     * Set the flag indicating whether the gnu plot folder has been created.
     * @param isCreated true if the gnu plot folder has been created, otherwise false.
     */
    private void setGNUPLOTFolderCreated(boolean isCreated) {
        m_isGNUPLOTFolderCreated = isCreated;
    }

    /**
     * Get the gnuplot data index.
     * @return the gnuplot data index.
     */
    private int getGNUPLOTDataIndex(){
        return ++m_iGNUPLOTDataIndex;
    }

    /**
     * Conduct sensitivity analyzes for a user interface.
     * @param outputFilename the output filename.
     * @param userInterfaceName a user interface.
     * @param outputContent the output content.
     * @throws IOException if there is an error, check the exception message for more details.
     */
    private void conductSensivitityAnalyses(String outputFilename, String userInterfaceName, StringBuilder outputContent) throws IOException {
        RMSensitivityAnalyser oAnalyser = RMSensitivityAnalyser.getInstance();

        File oOutputFile = new File(outputFilename);
        String strOutputFilename = oOutputFile.getName();
        String strOutputFilenameWithoutExtention = StringUtils.substringBeforeLast(strOutputFilename, ".");

        File oGNUPLOTFolder = new File(oOutputFile.getParentFile(), strOutputFilenameWithoutExtention + "_gnuplots");
        if (!isGNUPLOTFolderCreated()) {
            if (!oGNUPLOTFolder.exists()) {
                oGNUPLOTFolder.mkdirs();
            } else {
                FileUtils.cleanDirectory(oGNUPLOTFolder);
            }

            setGNUPLOTFolderCreated(true);
        }

        outputContent.append("#====================================================================\n# For user interface '").append(userInterfaceName).append("':\n");

        RMSensitivityAnalysisData[] oData = oAnalyser.doSensitivityAnalysis(userInterfaceName);
        for (int i = 0; i < oData.length; i++) {
            StringBuilder oGnuplotPLT = new StringBuilder();
            String strGNUPLOTPLTFilename = userInterfaceName + " - " + oData[i].getSAFigureCaption() + ".plt";
            
            outputContent.append("# gnuplot plt file: '").append(oGNUPLOTFolder.getName()).append(File.separator).append(strGNUPLOTPLTFilename).append("'\n");
            int iGNUPLOTDataIndex = getGNUPLOTDataIndex();
            outputContent.append("# gnuplot data index: ").append(iGNUPLOTDataIndex).append("\n");
            outputContent.append(oData[i].getGNUPLOTDataBlock(strOutputFilename, iGNUPLOTDataIndex, oGnuplotPLT)).append("\n\n");

            IOUtils.write(oGnuplotPLT.toString(), new java.io.FileOutputStream(new File(oGNUPLOTFolder, strGNUPLOTPLTFilename )));
        }
    }

    /**
     * Conduct a reliability prediction for a user interface.
     * @param userInterfaceName the user interface name.
     * @param outputContent the output content.
     */
    private void conductReliabilityPrediction(String userInterfaceName, StringBuilder outputContent) {
        RMReliabilityCalculator oCalculator = RMReliabilityCalculator.getInstance();

        InternalActivityType oIA = oCalculator.calculateReliabilityRelatedProbabilities(userInterfaceName);

        outputContent.append("#====================================================================\n# For user interface '").append(userInterfaceName).append("':\n");
        outputContent.append("# Predicted reliability: ").append(1.0 - oIA.getSfp() - oIA.getPfp()).append("\n");
    }

    /**
     * The main method.
     * @param args an array of command line arguments.
     */
    public static void main(String args[]) {
        RMReliabilityModeling oModeling = new RMReliabilityModeling(args);
        oModeling.doStart();
    }

    /**
     * Print the usage information.
     * @param oOptions the command line options.
     */
    private void printUsageInformation(Options oOptions) {
        HelpFormatter oHelpFormatter = new HelpFormatter();
        oHelpFormatter.setWidth(80);
        oHelpFormatter.printHelp(USAGE, HEADER, oOptions, FOOTER);
    }

    /**
     * Validate the input model.
     * @param strInputModelFilename the input model file name.
     * @param outputContent the output content.
     * @return true if the input model is valid, otherwise false.
     */
    private boolean validateInputRelabilityModeling(String strInputModelFilename, StringBuilder outputContent){
        printInformation("Validating the input model... ", false);
        RMInputModelManager oManager = RMInputModelManager.getInstance();
        try {
            oManager.setInputModelFilename(strInputModelFilename);
            outputContent.append("# The input model is valid.\n");
            printInformation("Done.", false);
            printInformation("The input model is valid.", true);

            return true;
        } catch (RMInputModelException oRMInputModelException) {
            outputContent.append(oRMInputModelException.getMessage()).append("\n");
            List<XmlError> oXMLErrors = oManager.getXMLErrors();
            if (oXMLErrors != null) {
                for (XmlError oXMLError : oXMLErrors) {
                    outputContent.append("Line ").append(oXMLError.getLine()).append(": ").append(oXMLError.getMessage()).append("\n");
                }
            }

            printInformation("Done.", false);
            printInformation("The input model is not valid. Check the output file for more details!", true);
            return false;
        }
    }
}
